<?php

namespace Fluency\Routing;

use Fluency\Request;
use Fluency\Executor;

/**
 * @package Fluency\Routing
 * @author  Ignas Rudaitis
 * @version 2.0.0
 */
class DecentralizedRouter implements Router
{
    const SHOULD_STOP = 'DecentralizedRouter::SHOULD_STOP';
    const PARAMETER_PATTERN = '/\:(?<name>\w+)/';
    const ROUTES_ATTRIBUTE = "Routes";

    private $executor;
    private $routes = array();
    private $patterns = array();
    private $parameters = array();

    public function __construct(Executor $executor)
    {
        $this->executor = $executor;
    }

    public function createRequest($url)
    {
        $segments = explode("/", trim($url, "/"));
        $actionSegmentCount = $this->countActionSegments($segments);
        if ($actionSegmentCount === null) {
            return null;
        }
        $action = join("/", array_slice($segments, 0, $actionSegmentCount));
        $remainder = join("/", array_slice($segments, $actionSegmentCount));
        return $this->matchRoutes($action, $remainder);
    }

    private function countActionSegments(array $allSegments)
    {
        $total = count($allSegments);
        for ($depth = 0; $depth <= $total; $depth++) {
            $action = join("/", array_slice($allSegments, 0, $depth));
            $flags = array();
            if ($this->executor->actionExists($action, $flags)) {
                return $depth;
            } elseif (in_array(self::SHOULD_STOP, $flags)) {
                return null;
            }
        }
    }

    private function matchRoutes($action, $remainder)
    {
        $this->parseRoutes($action);
        $request = Request::forAction($action);
        if (empty($this->patterns[$action])) {
            return $request;
        }
        foreach ($this->patterns[$action] as $pattern) {
            if (preg_match($pattern, $remainder, $matches)) {
                return $request->withParameters($this->extractNamedMatches($matches));
            }
        }
    }

    private function extractNamedMatches(array $matches)
    {
        $namedMatches = array();
        foreach ($matches as $key => $value) {
            if (is_string($key)) {
                $namedMatches[$key] = $value;
            }
        }
        return $namedMatches;
    }

    private function parseRoutes($action)
    {
        if (isset($this->routes[$action])) {
            return;
        }
        $meta = $this->executor->getActionMetadata($action);
        $this->routes[$action] = (array) $meta->getAttribute(static::ROUTES_ATTRIBUTE);
        foreach ($this->routes[$action] as $route) {
            preg_match_all(static::PARAMETER_PATTERN, $route, $matches);
            $this->patterns[$action][$route] = $this->convertRouteToRegex($route, $matches["name"]);
            $this->parameters[$action][$route] = $matches["name"];
        }
    }

    private function convertRouteToRegex($route, array $params)
    {
        $route = preg_quote($route, "#");
        foreach ($params as $param) {
            $route = str_replace("\:{$param}", "(?<{$param}>.+?)", $route);
        }
        return "#^{$route}$#";
    }

    public function createUrl($action, array $params = array())
    {
        $this->parseRoutes($action);
        if (empty($this->routes[$action])) {
            return $action;
        }
        foreach ($this->routes[$action] as $route) {
            foreach ($this->parameters[$action][$route] as $paramName) {
                if (!isset($params[$paramName])) {
                    continue 2; // skip to the next route
                }
                $route = str_replace(":{$paramName}", $params[$paramName], $route);
            }
            return $action . "/" . trim($route, "/");
        }
    }
}